﻿using System;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using Simhopp.Common;
using Server;
using System.Collections.Specialized;


namespace Simhopp.Server
{
    /// <summary>
    /// The ClientListener class provides a description of a client connection.
    /// </summary>
    public class ClientListener
    {
        #region FIELD REGION
        private readonly ClientHandler _server;
        private readonly TcpClient _tcpClient;
        private readonly Stream _stm;
        private readonly BinaryFormatter _binaryFormatter;
        private readonly int _clientNo;
        private static int _noOfClients = 0;
        #endregion
        #region CONSTRUCTOR REGION
        /// <summary>
        /// Constructor taking arguments.
        /// </summary>
        /// <param name="client">The TcpClient used to create a TCP/IP connection.</param>
        /// <param name="server">The ClientHandler object that the ClientListener is associated with.</param>
        public ClientListener(TcpClient client, ClientHandler server)
        {
            _server = server;
            _tcpClient = client;
            _stm = _tcpClient.GetStream();
            _binaryFormatter = new BinaryFormatter();
            _clientNo = _noOfClients++;
            User = null;

            SendObject(new NetworkCommand(Commands.Competition, server.Presenter.Competition));

            var newThread = new Thread(Run);
            newThread.Start();
        }
        #endregion
        #region PROPERTY REGION
        /// <summary>
        /// Gets the user associated with the client listener.
        /// </summary>
        public User User { get; private set; }
        #endregion
        #region METHOD REGION
        /// <summary>
        /// Listens to the client to receive objects sent to the server on the stream.
        /// </summary>
        private void Run()
        {
            do
            {
                if (_tcpClient.Connected && _stm.CanRead)
                {
                    try
                    {
                        ReceiveObject(_stm);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(@"Client disconnected");
                        MyLogger.Debug("Client disconnected - " + e.Message);
                    }
                }
            } while (_tcpClient.Connected);
            _server.DeleteFromClientListeners(this);
        }
        /// <summary>
        /// Gets a string representation of the user associated with the client listener.
        /// </summary>
        /// <returns>A string representation of the user associated with the client listener.</returns>
        public override string ToString()
        {
            if (User != null)
            {
                return User.UserName + " (" + User.AccountType + ")";
            }

            return _tcpClient.Client.RemoteEndPoint.ToString();
        }
        /// <summary>
        /// Receives an object sent on the stream and takes appropriate actions.
        /// </summary>
        /// <param name="stream">The stream from which to receive objects.</param>
        private void ReceiveObject(Stream stream)
        {
            try
            {
                var command = _binaryFormatter.Deserialize(stream) as NetworkCommand;
                ProcessObject(command);
            }
            catch (Exception e)
            {
                MyLogger.Debug(String.Format("Error deserializing object: {0}\n{1}", e.Message, e.StackTrace));
                _tcpClient.Close();
            }
        }
        /// <summary>
        /// Calls the appropriate method depending on the network command received.
        /// </summary>
        /// <param name="command">The network command received.</param>
        private void ProcessObject(NetworkCommand command)
        {
            LogConsole.WriteLine(String.Format("{0} command received", command.Command));
            if (command == null) return;
            switch (command.Command)
            {
                case Commands.Login:
                    ProcessLogin(command.Obj as LogOnInfo);
                    break;
                case Commands.Logout:
                    ProcessLogout();
                    break;
                case Commands.AddScore:
                    ProcessScore(command.Obj as Score);
                    break;
                case Commands.Competition:
                    ProcessCompetition(command.Obj as Competition);
                    break;
                case Commands.BeginCompetition:
                    ProcessBeginCompetition(command.Obj as Competition);
                    break;
                case Commands.SetCompetition:
                    ProcessSetActiveCompetition(command.Obj as Nullable<int>);
                    break;
            }
        }
        /// <summary>
        /// Starts the competition and updates the clients.
        /// </summary>
        /// <param name="competition">The competition sent from the administrator or head judge.</param>
        private void ProcessSetActiveCompetition(Nullable<int> competitionIndex)
        {
            if (competitionIndex != null)
            {
                _server.Presenter.SetActiveCompetition((int)competitionIndex);
                var allCompetitions = ReadCompetition.GetAllCompetitions();
                var listOfCompetition = new ListOfCompetitions(allCompetitions);
                SendObject(new NetworkCommand(Commands.AllCompetitions, listOfCompetition));

                

                _server.SendObjectToAll(new NetworkCommand(Commands.Competition, _server.Presenter.Competition));
            }
        }

        private void ProcessBeginCompetition(Competition competition)
        {
            _server.Presenter.SetCompetition(competition);
            _server.Presenter.Competition.NextRound();
            CompetitionMap.SaveCompetitionToDatabase(_server.Presenter.Competition);
            _server.SendObjectToAll(new NetworkCommand(Commands.Competition, _server.Presenter.Competition));
        }
        /// <summary>
        /// Proceeds to the next diver and updates the clients.
        /// </summary>
        private void ProcessNextDiver()
        {
            _server.Presenter.Competition.NextDiver();
            CompetitionMap.SaveCompetitionToDatabase(_server.Presenter.Competition);
            _server.SendObjectToAll(new NetworkCommand(Commands.NextDiver));
        }
        /// <summary>
        /// Proceeds to the next round and updates the clients.
        /// </summary>
        private void ProcessNextRound()
        {
            _server.Presenter.Competition.NextRound();
            CompetitionMap.SaveCompetitionToDatabase(_server.Presenter.Competition);
            _server.SendObjectToAll(new NetworkCommand(Commands.NextRound));
        }
        /// <summary>
        /// Performs the actions necessary to log out a user.
        /// </summary>
        private void ProcessLogout()
        {
            LogConsole.WriteLine(String.Format("User {0} logged out!", User.UserName));
            User = null;


            App.Current.Dispatcher.BeginInvoke((Action)delegate
            {
                _server.Judges.Refresh();
                _server.Viewers.Refresh();
            }); 
        }
        /// <summary>
        /// Performs the actions necessary to log in a user.
        /// </summary>
        /// <param name="logOnInfo">The login info received from the user.</param>
        private void ProcessLogin(LogOnInfo logOnInfo)
        {
            foreach (var judge in _server.Presenter.Competition.Judges)
            {
                if (judge.UserName.Equals(logOnInfo.UserName))
                {
                    if (_server.IsUserLoggedIn(judge))
                    {
                        LogConsole.WriteLine(String.Format("Login failed for {0}!", logOnInfo.UserName));
                        SendObject(new NetworkCommand(Commands.LoginFailed, string.Format("Det användarnamnet är redan inloggat")));
                        return;
                    }
                    LogConsole.WriteLine(String.Format("Login verified for {0}!", judge.UserName));
                    User = judge;
                    SendObject(new NetworkCommand(Commands.LoginVerified, User));
                    if (User.AccountType == AccountTypes.Administrator)
                    {
                        var allCompetitions = ReadCompetition.GetAllCompetitions();
                        var listOfCompetition = new ListOfCompetitions(allCompetitions);
                        // SendObject(new NetworkCommand(Commands.AllCompetitions));
                        SendObject(new NetworkCommand(Commands.AllCompetitions, listOfCompetition));
                    }
                    break;
                }
            }
            if (User != null)
            {
                App.Current.Dispatcher.BeginInvoke((Action)delegate
                {
                    _server.Judges.Refresh();
                    _server.Viewers.Refresh();
                });
                return;
            }
            LogConsole.WriteLine(String.Format("Login failed for {0}!", logOnInfo.UserName));
            SendObject(new NetworkCommand(Commands.LoginFailed, string.Format("Felaktigt användarnamn")));
        }
        /// <summary>
        /// Saves a competition object received from an administrator or a head judge.
        /// </summary>
        /// <param name="competition">The competition object to save.</param>
        private void ProcessCompetition(Competition competition)
        {
            _server.Presenter.SetCompetition(competition);
            _server.SendObjectToAll(new NetworkCommand(Commands.Competition, competition));
        }
        /// <summary>
        /// Performs the necessary actions to set a score for the current dive/diver.
        /// </summary>
        /// <param name="score">The score to be set.</param>
        private void ProcessScore(Score score)
        {
            SendObject(new NetworkCommand(Commands.ScoreReceived, "The score was successfully added! Please await the other judges."));
            _server.Presenter.AddScore(score);
        }
        /// <summary>
        /// Sends a network command to the client listener.
        /// </summary>
        /// <param name="o">The network command to send.</param>
        public void SendObject(NetworkCommand o)
        {
            if (!_tcpClient.Connected || !_stm.CanWrite) return;
            try
            {
                _binaryFormatter.Serialize(_stm, o);
            }
            catch (Exception e)
            {
                Console.WriteLine(@"Client disconnected");
                Console.WriteLine(@"Client disconnected - " + e.Message + "\n" + e.StackTrace);
                if ((e as SerializationException) != null)
                    Console.WriteLine(@"Serialiseringsfel: " + (e as SerializationException).Source + "\n" +
                                      (e as SerializationException).Data);
            }
        }
        /// <summary>
        /// Closes the connection to the client.
        /// </summary>
        public void Stop()
        {
            _tcpClient.Close();
        }
        #endregion
    }
}
